/**
 * 当我们定义一个变量不确定类型的时候有两种解决方式：
    使用any
    使用any定义时存在的问题：虽然 以 知道传入值的类型但是无法获取函数返回值的类型；另外也失去了ts类型保护的优势
    使用泛型
    泛型指的是在定义函数/接口/类型时，不预先指定具体的类型，而是在使用的时候在指定类型限制的一种特性
*/
namespace _6 {

    /***
     * 
     * 类似于函数传参，传什么数据类型，T就表示什么数据类型， 使用表示，T也可以换成任意字符串
     */
    function testT<T>(arg: T): T {
        console.log(arg);
        return arg;
    }

    /***
     * 接口
     */
    interface Search {
        <T, Y>(name: T, age: Y): T
    }
    let fn: Search = function <T, Y>(name: T, id: Y): T {
        console.log(name, id)
        return name;
    }
    // fn<string,number>("a",1)

    /***约束 */
    interface Person {
        name: string;
        age: number;
    }
    function student<T extends Person>(arg: T): T {
        return arg;
    }
    //   student({name:'lili'});//类型 "{ name: string; }" 中缺少属性 "age"，但类型 "Person" 中需要该属性
    //   student({ name: "lili" , age:'11'});//不能将类型“string”分配给类型“number”
    student({ name: "lili", age: 11 });


    /***
     * Partial
    partial<T>的作用就是将某个类型中的属性全部变为可选项?
     */
    interface Person {
        name: string;
        age: number;
    }
    function student2<T extends Person>(arg: Partial<T>): Partial<T> {
        return arg;
    }
    student2({ name: 'lili' });

    //============================不常用===================================
    /***
     * Record<K extends keyof any, T>的作用是将K中所有的属性转换为T类型
     */
    interface PageInfo {
        title: string
    }
    type Page = 'home' | 'about' | 'other';
    const x: Record<Page, PageInfo> = {
        home: { title: "xxx" },
        about: { title: "aaa" },
        other: { title: "ccc" },
    };
    /***
     * Pick<T, K extends keyof T>的作用是将某个类型中的子属性挑出来，变成包含这个类型部分属性的子类型
     */
    interface Todo {
        title: string,
        desc: string,
        time: string
    }
    type TodoPreview = Pick<Todo, 'title' | 'time'>;
    const todo: TodoPreview = {
        title: '吃饭',
        time: '明天'
    }
    /***
     * 
     * Exclude<T,U>的作用是将某个类型中属于另一个类型的属性移除掉
     */
    type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
    const t: T0 = 'b';



    //===============================================================
    // https://developer.aliyun.com/article/1069311
    /***
     * TS 泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字继承 object 类型的子类型，然后使用 keyof 操作符获取 T 类型的所有键，
     * 它的返回 类型是联合 类型，最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型
     */

    function prop<T, K extends keyof T>(obj: T, key: K) {
        return obj[key]
    }
    let o = { a: 1, b: 2, c: 3 }
    prop(o, 'a')

    // prop(o, 'd') 
    //，我们需要约束一下这个o里面并没有的东西，此时就会报错发现找不到
    //通过提示，我门可以看到类型"d"的参数不能赋给类型"a"|"b"|"c"的参数

    /**
    * 
    * 类
        注意类声明泛型和类内部函数生成泛型
    */
    class Anim<T> {
        // name: T;
        // constructor(name: T) {
        //     this.name = name;
        // }
        action<T>(say: T) {
            console.log(say)
        }
    }
    let cat = new Anim<string>();
    cat.action(12)



    /***泛型识别 */

    class A {
        str: string = 'a'
    }
    class B extends A {
        str = "b"
    }
    class C extends A {
        str = "c"
        constructor(str: string) {
            super()
            this.str = str
        }
    }

    let arr: Array<A> = [new B(), new C("c"), new A()];
    let originMenusMap: Map<string, C | null> = new Map<string, C | null>(arr.map<[string, C | null]>(item => [item.str, (item instanceof C) ? item : null]));
    // originMenusMap.forEach((value, key) => {
    //     console.log(key, typeof value)
    // });

    function test2<T extends U, U extends number[]>(t: T, u: U): number {
        return t.length + u.length
    }
}
